Skip to content

feat(generate): multi-level mesh undo with a bounded per-model stack - #404

Merged
lightningpixel merged 1 commit into
devfrom
feat/304-multi-level-undo
Nov 11, 2026
Merged

lightningpixel merged 1 commit into
devfrom
feat/304-multi-level-undo

Conversation

@lightningpixel

Copy link
Copy Markdown
Owner

Closes #304

Finding

Edits never overwrite their input: every op (/optimize/op/{id}, /arrange, /transform, decimate, smooth) writes a new GLB next to it, and nothing deletes them. A stack of URLs is therefore enough to restore earlier meshes, with no snapshot copies and no extra disk use.

The store already had a URL history with redo. What was broken:

  1. Unbounded: the history had no size limit.
  2. Crossed models: setCurrentJob never reset it. After an import, a library open, Add to Scene or a workflow run, undo jumped back to the previous model.
  3. First edit not undoable: callers set outputUrl before pushing, so a mesh that reached the viewer unrecorded (workflow output, Add to Scene) couldn't be undone back to.

Changes

  • New pure module meshHistory.ts: a stack capped at 20 undo steps, with record / record-edit / step back / step forward and undo/redo depth.
  • Single edit entry point applyMeshEdit(url): it records the edit from the mesh on screen, seeding that mesh first if it isn't current. Smooth, decimate and agent mesh_update results all go through it.
  • Loads: pushMeshUrl now only records a freshly loaded mesh.
  • Reset per model: setCurrentJob resets the stack to the new job's mesh, even for the same id, since Add to Scene reuses workflow-output.
  • UI: Undo/Redo are disabled when there's nothing to step to, the tooltips show the step count, and Undo has a depth badge.

Merge note

Conflicts with #403 on the test:node line of package.json only (both add a test file). Whichever merges second needs a one-line rebase; I'll handle it.

Not done

  • The Undo on a chat actions card still steps back once, not "the whole turn".
  • An agent edit finishing after a model switch still lands on the new model. This is pre-existing; a job-id guard would fix it.
  • History is in memory only, as before.

Test plan

  • meshHistory.test.ts (new): 12/12, including "3 edits then 3 undos restore the initial mesh"
  • pre-push hook (npm test, full suite) passed
  • eslint on the changed files: clean
  • Manual: smooth → decimate → agent edit, then 3× Undo / Redo; switch model and check Undo is disabled

Mesh edits never overwrite their input (every op writes a new workspace
file), so undo only needs the chain of URLs. Move it into a pure
meshHistory module and route every edit through one store action:

- applyMeshEdit(url) records the edit from the mesh currently shown,
  seeding it as the baseline when it was never recorded (workflow
  outputs, Add to Scene), so the first edit is always undoable.
- The stack is capped at 20 undo steps; a new edit drops the redo branch.
- setCurrentJob resets the stack, so undo never jumps to another model
  (import, library open, Add to Scene and workflow runs used to keep it).
- Smooth, decimate and agent mesh tools all go through applyMeshEdit.
- The undo button shows the number of available steps.

Closes #304
@lightningpixel
lightningpixel force-pushed the feat/304-multi-level-undo branch from 796e74c to 6d3f28d Compare November 11, 2026 10:22
@lightningpixel
lightningpixel merged commit b88b056 into dev Nov 11, 2026
4 checks passed
@lightningpixel
lightningpixel deleted the feat/304-multi-level-undo branch November 11, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant